/*******************************************************************************
 * Level Master 2000
 * Copyright (C) 2010 Christian Herzog
 * For more information see http://code.google.com/p/levelmaster2000/
 *
 * This file is licensed under the terms of the MIT license, see
 * http://www.opensource.org/licenses/mit-license.php for further information.
 ******************************************************************************/

#include "Communicator.h"

#include "IPBObject.h"

#include <QMessageBox>
#include <QTextEdit>
#include <QTextStream>
#include <QVariant>
#include <QWebFrame>
#include <QXmlStreamWriter>

Communicator::Communicator( QWidget *parent, QWebFrame *frame )
    : QObject( parent ),
    frame( frame )
{
//#ifdef DEBUG_OUTPUT
//    //Create some debug output window:
//    debugText = new QTextEdit( parent );
//    debugText->resize( 500, 100 );
//    debugText->show();
//#endif


}

void Communicator::parseCommands( QDomElement command )
{
    if( command.tagName() != "command" )  //TODO: make me a const var!
    {
        Logger::instance()->addLogMessage( Logger::LL_WARNING,
                               "Communicator::parseCommands",
                               "QDomElement not a command" );
        return;
    }

    while( !command.isNull() )
    {
        QString type = command.attribute( "type" );
        if( type == "entityData" )
        {
            parseEntityData( command );
        }
        else if( type == "requestSelectEntity" )
        {
            parseRequestSelectEntity( command );
        }
        else if( type == "sceneHierarchy" )
        {
            parseSceneHierarchy( command.firstChildElement() );
        }
        else if( type == "componentClassList" )
        {
            parseComponentClassList( command.firstChildElement() );
        }
        else if( type == "newEntity" )
        {
            parseNewEntity( command.firstChildElement() );
        }
        else if( type == "newGroup" )
        {
            parseNewGroup( command.firstChildElement() );
        }
        else if( type == "deleteObjects" )
        {
            parseDeleteObjects( command.firstChildElement() );
        }
        else if( type == "newComponent" )
        {
            parseNewComponent( command.firstChildElement() );
        }
        else if( type == "deleteComponent" )
        {
            parseDeleteComponent( command.firstChildElement() );
        }
        else if( type == "levelState" )
        {
            parseLevelState( command.firstChildElement() );
        }
        else if( type == "getEntityXml" )
        {
            parseGetEntityXml( command );
        }
        else if( type == "getComponentXml" )
        {
            parseGetComponentXml( command );
        }
        else
        {
            Logger::instance()->addLogMessage( Logger::LL_WARNING,
                                   "Communicator",
                                   "Unknown comman type: " + type );
        }

        //TODO: is this even possible?
        command = command.nextSiblingElement( "command" );
    }
}

void Communicator::parseEntityData( QDomElement node )
{
    QDomElement entityNode = node.firstChildElement();

    //TODO: does this still work?
    if( entityNode.isNull() )
    {
        return;
    }

    QDomElement componentNode = entityNode.firstChildElement();

    QString entityName = entityNode.attribute( "name" );

    emit parsedEntityDataBegin( entityName );

    while( !componentNode.isNull() )
    {
        QString componentName = componentNode.attribute( "name" );
        QMap<QString, QVariant> properties;

        QDomElement propertyNode = componentNode.firstChildElement();

        while( !propertyNode.isNull() )
        {
            bool addProperty = true;
            QString type = propertyNode.attribute( "type" );
            if( type == "flash.geom::Point" )
            {
                //<registrationPoint type="flash.geom::Point" value="(x=128, y=32)"/>
                QString point = propertyNode.attribute( "value" );
                QStringList pointComponents;

                //TODO: or would it be better to not display property?
                //Actually, flash shouldn't send NULL properties... fix it there!
                //Otherwise, if point is null, and something is entered,
                //a new point should be created...
                if( point.compare( "null" ) == 0 )
                {
                    pointComponents.push_back( "0" );
                    pointComponents.push_back( "0" );
                }
                else
                {
                    point.replace( "(", "" );
                    point.replace( ")", "" );
                    point.replace( "x=", "" );
                    point.replace( "y=", "" );
                    pointComponents = point.split( ", " );
                }

                QString name = propertyNode.nodeName();

                properties[name] = QPointF( pointComponents[0].toDouble(), pointComponents[1].toDouble() );
            }
            else if( type == "String" )
            {
                QString value = propertyNode.attribute( "value" );
                QString name = propertyNode.nodeName();

                properties[name] = value;
            }
            else if( type == "Double"
                     || type == "Number" )
            {
                QString value = propertyNode.attribute( "value" );
                QString name = propertyNode.nodeName();

                properties[name] = value.toDouble();
            }
            else if( type == "Boolean" )
            {
                QString value = propertyNode.attribute( "value" );
                QString name = propertyNode.nodeName();

                bool val;
                if( value == "true" )
                {
                    val = true;
                }
                else
                {
                    val = false;
                }

                properties[name] = val;
            }
            else if( type == "int" )
            {
                QString value = propertyNode.attribute( "value" );
                QString name = propertyNode.nodeName();

                properties[name] = value.toInt();

            }
            else if( type == "com.pblabs.engine.entity::PropertyReference" )
            {
                QString value = propertyNode.attribute( "value" );
                QString name = propertyNode.nodeName();

                properties[name] = value;
            }
            else if( type == "com.pblabs.engine.core::ObjectType" )
            {
                QString value = propertyNode.attribute( "value" );
                QString name = propertyNode.nodeName();

                properties[name] = value;
            }
            else if( type == "componentReference" )
            {
                QString value = propertyNode.attribute( "value" );
                QString name = propertyNode.nodeName();

                properties[name] = value;
            }
            else
            {
                Logger::instance()->addLogMessage( Logger::LL_WARNING,
                                       "Communicator::parseEntityData",
                                       "Unknown data type: " + type
                                       + " for property " + propertyNode.nodeName() );
                addProperty = false;
            }

            propertyNode = propertyNode.nextSiblingElement();
        }

        emit parsedComponentData( componentName, properties );

        componentNode = componentNode.nextSiblingElement();
    }

    emit parsedEntityDataEnd( entityName );
}

void Communicator::parsePBObject( QDomElement objectNode, Group *currentGroup, QList<IPBObject*> *objects )
{
    while( !objectNode.isNull() )
    {
        QString objectType = objectNode.nodeName();

        if( objectType == "group" )
        {
            Group *group = new Group( objectNode.attribute( "name" ), currentGroup );
            objects->push_back( group );

            Logger::instance()->addLogMessage(Logger::LL_INFO, "communicator", "group: " + group->name);

            //Also parse content of group:
            parsePBObject( objectNode.firstChildElement(), group, objects );
        }
        else if( objectType == "entity" )
        {
            //Get entity data (name):
            Entity *entity = new Entity( objectNode.attribute( "name" ) );
            entity->group = currentGroup;

            Logger::instance()->addLogMessage(Logger::LL_INFO, "communicator", "entity: " + entity->name);

            QDomElement componentNode = objectNode.firstChildElement();
            while( !componentNode.isNull() )
            {
                //Get entity data (component name and type):
                QString componentName = componentNode.attribute( "name" );

                //"com.pblabs.box2D::Box2DDebugComponent"
                QString type = componentNode.attribute( "type" );
                QStringList package = type.split("::");
                ClassInfo classInfo;

                if( package.size() == 1 )
                {
                    classInfo.name = package[0];
                }
                else
                {
                    classInfo.name = package[1];
                    classInfo.package = package[0].split(".");
                }
                Component *component = new Component( componentName, classInfo );

                entity->components.push_back( component );

                componentNode = componentNode.nextSiblingElement();
            }

            objects->push_back( entity );
        }

        objectNode = objectNode.nextSiblingElement();
    }
}

void Communicator::parseSceneHierarchy( QDomElement objectNode )
{
    QList<IPBObject*> pbObjects;

    parsePBObject( objectNode, 0, &pbObjects );

    emit parsedLoadedSceneHierarchy( pbObjects );
}

void Communicator::parseRequestSelectEntity( QDomElement commandNode )
{
    QString entityName = commandNode.attribute( "entityName" );

    emit parsedRequestSelectEntity( entityName );
}

void Communicator::parseNewEntity( QDomElement entityNode )
{
    //"<entity name=\"" + entityName + "\" />"
    QString entityName = entityNode.attribute( "entityName" );
    QString groupName = entityNode.attribute( "groupName" );

    emit parsedNewEntity( entityName, groupName );
}

void Communicator::parseNewGroup( QDomElement groupNode )
{
    //"<entity name=\"" + groupName + "\" />"
    QString groupName = groupNode.attribute( "groupName" );
    QString parentGroup = groupNode.attribute( "parentGroup" );

    emit parsedNewGroup( groupName, parentGroup );
}

void Communicator::sendLoadLevel( int index, QString name )
{
    QString xmlString;
    QXmlStreamWriter stream( &xmlString );
    stream.setAutoFormatting( false );
    stream.writeStartElement( "command" );
    stream.writeAttribute( "type", "loadLevel" );
    stream.writeAttribute( "index", QString::number( index ) );
    stream.writeAttribute( "name", name );
    stream.writeEndElement();

    sendXmlString( xmlString );
}

//TODO: arguments needed what for?!
void Communicator::sendRequestSaveLevel( int index, QString name )
{
    QString xmlString;
    QXmlStreamWriter stream( &xmlString );
    stream.setAutoFormatting( false );
    stream.writeStartElement( "command" );
    stream.writeAttribute( "type", "requestSaveLevel" );
    stream.writeAttribute( "index", QString::number( index ) );
    stream.writeAttribute( "name", name );
    stream.writeEndElement();

    sendXmlString( xmlString );
}

void Communicator::workspaceChanged( QString newWorkspace )
{
    newWorkspace = newWorkspace.replace("\\", "/");

    QString xmlString;
    QXmlStreamWriter stream( &xmlString );
    stream.setAutoFormatting( false );
    stream.writeStartElement( "command" );
    stream.writeAttribute( "type", "workspaceChanged" );
    stream.writeAttribute( "path", newWorkspace );
    stream.writeEndElement();

    sendXmlString( xmlString );
}

void Communicator::sendUpdateComponentProperty( QString component, QString property, QString value )
{
    //TODO: add attribute entityName, change component to componentName
    QString xmlString;
    QXmlStreamWriter stream( &xmlString );
    stream.setAutoFormatting( false );
    stream.writeStartElement( "command" );
    stream.writeAttribute( "type", "updateProperty" ); //TODO: const me!
    stream.writeAttribute( "component", component ); //TODO: const me!
    {
        stream.writeStartElement( property );
        stream.writeAttribute( "value", value );
        stream.writeEndElement();
    }
    stream.writeEndElement();

    sendXmlString( xmlString );
}

void Communicator::sendSelectEntity( QString entityName )
{
    QString xmlString;
    QXmlStreamWriter stream( &xmlString );
    stream.setAutoFormatting( false );
    stream.writeStartElement( "command" );
    stream.writeAttribute( "type", "selectEntity" ); //TODO: const me!
    {
        stream.writeStartElement( "entity" );
        stream.writeAttribute( "name", entityName  );
        stream.writeEndElement();
    }
    stream.writeEndElement();

    sendXmlString( xmlString );
}

void Communicator::sendSelectEntities( QList<Entity*> entities )
{
    if( entities.count() == 0)
    {
        int i = 0;
        i++;
    }


    QString xmlString;
    QXmlStreamWriter stream( &xmlString );
    stream.setAutoFormatting( false );
    stream.writeStartElement( "command" );
    stream.writeAttribute( "type", "selectEntities" ); //TODO: const me!
    {
        stream.writeStartElement( "entities" );
        {
            foreach( Entity *entity, entities )
            {
                stream.writeStartElement( "entity" );
                stream.writeAttribute( "name", entity->name );
                stream.writeEndElement();
            }
        }
        stream.writeEndElement();
    }
    stream.writeEndElement();

    sendXmlString( xmlString );
}


void Communicator::sendXmlString( QString xmlString )
{
    //Replace some special chars, otherwise evaluateJavaScript won't work.
    xmlString.replace("\"", "\\\"");
    xmlString.replace("\n", "");
    xmlString.replace("\t", "");

    QString sendString = "sendToActionScript(\"" + xmlString + "\")";

#ifdef DEBUG_OUTPUT
    //Display the current send string in the debug output window.
    //debugText->setText( "receivedFromJavaScript(\"" + xmlString + "\");" );
    Logger::instance()->addLogMessage( Logger::LL_DEBUG, "Communicator::sendXmlString", "communicator.receivedFromJavaScript(\"" + xmlString + "\");" );
#endif

    frame->evaluateJavaScript( sendString );
}

void Communicator::sendCreateComponent( QString entityName, QString componentName, QString componentClassName )
{
    QString xmlString;
    QXmlStreamWriter stream( &xmlString );
    stream.setAutoFormatting( false );
    stream.writeStartElement( "command" );
    stream.writeAttribute( "type", "createComponent" );
    stream.writeAttribute( "entityName", entityName );
    stream.writeAttribute( "componentName", componentName );
    stream.writeAttribute( "componentClass", componentClassName );
    stream.writeEndElement();

    sendXmlString( xmlString );
}

void Communicator::sendCreateEntity( QString entityName, QString groupName )
{
    QString xmlString;
    QXmlStreamWriter stream( &xmlString );
    stream.setAutoFormatting( false );
    stream.writeStartElement( "command" );
    stream.writeAttribute( "type", "createEntity" );
    stream.writeAttribute( "entityName", entityName );
    stream.writeAttribute( "groupName", groupName );
    stream.writeEndElement();

    sendXmlString( xmlString );
}

void Communicator::sendCreateGroup( QString groupName, QString parentGroup )
{
    QString xmlString;
    QXmlStreamWriter stream( &xmlString );
    stream.setAutoFormatting( false );
    stream.writeStartElement( "command" );
    stream.writeAttribute( "type", "createGroup" );
    stream.writeAttribute( "groupName", groupName );
    stream.writeAttribute( "parentGroup", parentGroup );
    stream.writeEndElement();

    sendXmlString( xmlString );
}

void Communicator::sendGetEntityXml( QString name )
{
    QString xmlString;
    QXmlStreamWriter stream( &xmlString );
    stream.setAutoFormatting( false );
    stream.writeStartElement( "command" );
    stream.writeAttribute( "type", "getEntityXml" );
    stream.writeAttribute( "name", name );
    stream.writeEndElement();

    sendXmlString( xmlString );
}

void Communicator::sendSetEntityXml( QString name, QString xml )
{
    QString xmlString;
    QXmlStreamWriter stream( &xmlString );
    stream.setAutoFormatting( false );
    stream.writeStartElement( "command" );
    stream.writeAttribute( "type", "setEntityXml" );
    stream.writeAttribute( "name", name );
    stream.writeAttribute( "xml", xml );
    stream.writeEndElement();

    sendXmlString( xmlString );
}

void Communicator::sendGetComponentXml( QString entityName, QString componentName  )
{
    QString xmlString;
    QXmlStreamWriter stream( &xmlString );
    stream.setAutoFormatting( false );
    stream.writeStartElement( "command" );
    stream.writeAttribute( "type", "getComponentXml" );
    stream.writeAttribute( "entityName", entityName );
    stream.writeAttribute( "componentName", componentName );
    stream.writeEndElement();

    sendXmlString( xmlString );
}

void Communicator::sendSetComponentXml( QString entityName, QString componentName, QString xml )
{
    QString xmlString;
    QXmlStreamWriter stream( &xmlString );
    stream.setAutoFormatting( false );
    stream.writeStartElement( "command" );
    stream.writeAttribute( "type", "setComponentXml" );
    stream.writeAttribute( "entityName", entityName );
    stream.writeAttribute( "componentName", componentName );
    stream.writeAttribute( "xml", xml );
    stream.writeEndElement();

    sendXmlString( xmlString );
}


void Communicator::sendDeleteComponent( QString entityName, QString componentName )
{
    QString xmlString;
    QXmlStreamWriter stream( &xmlString );
    stream.setAutoFormatting( false );
    stream.writeStartElement( "command" );
    stream.writeAttribute( "type", "deleteComponent" );
    stream.writeAttribute( "entityName", entityName );
    stream.writeAttribute( "componentName", componentName );
    stream.writeEndElement();

    sendXmlString( xmlString );
}

void Communicator::sendDeleteObjects( QStringList objects )
{
    QString xmlString;
    QXmlStreamWriter stream( &xmlString );
    stream.setAutoFormatting( false );
    stream.writeStartElement( "command" );
    stream.writeAttribute( "type", "deleteObjects" );

    stream.writeStartElement( "objects" );
    {
        foreach( QString name, objects )
        {
            stream.writeStartElement( "object" );
            stream.writeAttribute( "name", name );
            stream.writeEndElement();
        }
    }
    stream.writeEndElement();

    stream.writeEndElement();

    sendXmlString( xmlString );
}


void Communicator::parseDeleteObjects( QDomElement objectsNode )
{
    QStringList objects;

    QDomElement objectNode = objectsNode.firstChildElement();

    while( !objectNode.isNull() )
    {
        objects.push_back( objectNode.attribute( "name" ) );

        objectNode = objectNode.nextSiblingElement();
    }

    emit parsedDeleteObjects( objects );
}

void Communicator::parseNewComponent( QDomElement componentNode )
{
    QString entityName      = componentNode.attribute( "owner" );
    QString componentName   = componentNode.attribute( "name" );
    QString componentClass  = componentNode.attribute( "class" );

    emit parsedNewComponent( entityName, componentName, componentClass );
}

void Communicator::parseDeleteComponent( QDomElement componentNode )
{
    //"<component owner=\"" + entityName + "\" name=\"" + componentName + "\" />"
    QString entityName = componentNode.attribute( "owner" );
    QString componentName = componentNode.attribute( "name" );

//    QTreeWidgetItem *componentItem = findComponentTreeItemByName( entityName, componentName );
//
//    if( 0 != componentItem )
//    {
//            componentItem->parent()->removeChild( componentItem );
//    }

    emit parsedDeleteComponent( entityName, componentName );
}

void Communicator::parseLevelState( QDomElement levelNode )
{
    QString str;
    QTextStream stream( &str, QIODevice::ReadWrite );

    levelNode.save( stream, 2 );

    QString levelState = stream.readAll();

    emit parsedLevelState( levelState );
}

void Communicator::parseGetEntityXml( QDomElement commandNode )
{
    QString str;
    QTextStream stream( &str, QIODevice::ReadWrite );

    QDomElement eXmlNode = commandNode.firstChildElement();
    eXmlNode.save( stream, 2 );

    QString entityXml = stream.readAll();

    QString name = commandNode.attribute( "name" );

    emit parsedGetEntityXml( name, entityXml );
}

void Communicator::parseGetComponentXml( QDomElement commandNode )
{
    QString str;
    QTextStream stream( &str, QIODevice::ReadWrite );

    QDomElement cXmlNode = commandNode.firstChildElement();
    cXmlNode.save( stream, 2 );

    QString componentXml = stream.readAll();

    QString entityName = commandNode.attribute( "entityName" );
    QString componentName = commandNode.attribute( "componentName" );
    QString className = commandNode.attribute( "className" );

    emit parsedGetComponentXml( entityName, componentName, className, componentXml );
}

void Communicator::parseComponentClassList( QDomElement componentNode )
{
    QList<ClassInfo> componentInfo;

    //Parse incoming component list and stuff it into componentInfo:
    while( !componentNode.isNull() )
    {
        ClassInfo compInfo;
        QString name= componentNode.attribute( "name" );
        QStringList packageAndClass = name.split( "::" );

        if( packageAndClass.count() == 1 )
        {
            compInfo.name = packageAndClass[0];
        }
        else if( packageAndClass.count() == 2 )
        {
            compInfo.package = packageAndClass[0].split( "." );
            compInfo.name = packageAndClass[1];
        }

        componentInfo.push_back( compInfo );

        componentNode = componentNode.nextSiblingElement();
    }

    emit parsedComponentInfo( componentInfo );
}
